/*
 * server.c
 *
 *  Created on: Feb 9, 2012
 *      Author: Eric Williams
 */
// ====================== HEADER FILES ======================
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/stat.h> /* For mode constants */
#include <fcntl.h> /* For O_* constants */
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <sys/types.h>

// ================= DATA TYPE DECLARATIONS =================

typedef unsigned char byte;

#define PRINT_RED 	printf("\033[22;31m")
#define PRINT_WHITE	printf("\033[10;37m")
#define MAX_CLIENTS	10
#define SECTOR_SIZE 512
#define FIVE_MIN	300   //Five minutes in seconds
//#define DEBUG
//#define NUM_OF_SECTORS	500
//#define __USE_GNU
# define O_DIRECT	 040000	/* Direct disk access.	*/

const char image_name[255] = "disk1.img";  //This is where image file is and our resource that we are accessing


volatile int ch_pid = -1;
FILE *fp;
FILE *client_fp;
int sector_file;
//byte buff[SECTOR_SIZE];  //Each character is byte sized

byte* buffer;

//TODO Will need to match ring buf structure
typedef struct
{
	int sector_num;
	int served;  //0 or 1 means served
	int finished; //0 or 1
	byte data[SECTOR_SIZE];  //Each character is byte sized

} test_struct;  //Ring_buffer

//Child datatypes needed
int client_pid;
int shm_id;
int client_count = 0;  //Iterator through queue
int active_clients = 0;  //Holds size of active clients and the index of next client

char name[255];
test_struct * shared_addr[MAX_CLIENTS];

// ================== FUNCTION PROTOTYPES ==================
int read_sector(int sector_num, byte * data);
void sig_handler(int sig_num);
void alarm_handler(int sig_num);
void unblock_signal(int signo);
void block_signal(int signo);
void install_sighandler(int signo, void (*handler)(int));
void delete_client(test_struct ** shared_addr, int index);



int main(int argc, char *argv[])
{

	if(argc!=2)
	{
		printf("Invalid number of arguments\n");
		return 0;
	}

	if (strcmp(argv[1], "stop")==0)
	{
		fp= fopen("ch_id", "r");  //Doesn't wipe away file
		//fseek(fp, 0,SEEK_SET);
		if(fscanf(fp, "%d", &ch_pid)!=EOF)
		{
			fclose(fp);
			//PRINT_RED;
			printf("My child is gone\n");
			//PRINT_WHITE;
			//printf("REDI will see him in the resurrection\n");
			kill(ch_pid, SIGKILL);
		}
		else
			printf("I have no children\n");

		printf("ch_id= %d", ch_pid);
	}
	else if (strcmp(argv[1], "start")==0)
	{
		//TODO may need to check if there are no other kids out there in case server is started twice
		//fp= fopen("ch_id", "w+");//Clears file,
		int fd_ch_id = open("ch_id", O_RDWR|O_CREAT, 0777 );  //S_IRWXU|S_IRWXG|S_IRWXO
		int clients_id =  open("clients", O_RDWR|O_CREAT, 0777 );  //S_IRWXU|S_IRWXG|S_IRWXO
		close(fd_ch_id);
		close(clients_id);
		fp= fopen("ch_id", "w+");//Clears file,
		client_fp= fopen("clients", "w+");  //Creates the file
		//chmod(sectorFile, 00755);  //May use this for the file problems
		//Sector initializations
		off_t num_sectors = 0;
		sector_file = open(image_name, O_RDONLY|O_DIRECT);
		off_t disk_size =  lseek(sector_file, 0, SEEK_END); // seek to end of file, off_t is a long_int, returns bytes


		num_sectors = disk_size/SECTOR_SIZE;

		ch_pid = fork();

		if(ch_pid!=0)  //Parent process
		{

			fprintf(fp, "%d, %ld", ch_pid, num_sectors);
			fclose(fp);
			fclose(client_fp);
			printf("Child id = %d\n", ch_pid);

#ifdef DEBUG
			 int status;
			do {

				  	  	pid_t  w = waitpid(ch_pid, &status, WUNTRACED | WCONTINUED);
			            if (w == -1) {
			                perror("waitpid");
			                exit(EXIT_FAILURE);
			            }

			           if (WIFEXITED(status)) {
			                printf("exited, status=%d\n", WEXITSTATUS(status));
			            } else if (WIFSIGNALED(status)) {
			                printf("killed by signal %d\n", WTERMSIG(status));
			            } else if (WIFSTOPPED(status)) {
			                printf("stopped by signal %d\n", WSTOPSIG(status));
			            } else if (WIFCONTINUED(status)) {
			                printf("continued\n");
			            }
			        } while (!WIFEXITED(status) && !WIFSIGNALED(status));

#endif


			printf("Exiting\n");
			exit(0);
		}
		else if(ch_pid==0)		//Child process
		{

			// ================== SERVER BEGINS ==================
			//where real work happens


			//signal(SIGUSR1, sig_handler);
			//buffer = malloc(SECTOR_SIZE);

			FILE *client_fp = fopen("clients", "a+");

			//int sector_data = open("sector_data", O_RDWR|O_CREAT);
			//FILE *sector_data = fopen("new_data", "w+");

			install_sighandler(SIGUSR1, sig_handler);  //Start listening out for client registration
			while(1)
			{
				if(active_clients==0)
				{
					//Start timer so we quit after 5min
					signal(SIGALRM, alarm_handler);
					alarm(FIVE_MIN);
					pause();
				}


				else
				{
					block_signal(SIGUSR1); //We want to block instead of ignore, if we ignore...we will never get it
					//Round robin --Serve next one
					if(client_count >= active_clients)
					{
						client_count = 0;

					}

					//signal(SIGUSR1, SIG_IGN);  //Used for ignoring the signal

					if(!shared_addr[client_count]->finished && !shared_addr[client_count]->served)
					{
						//printf("Serving client %d\n", client_count);
						if(read_sector(shared_addr[client_count]->sector_num, shared_addr[client_count]->data)!=0)
							printf("Read sector failure\n");

						//if(write(sector_data, shared_addr[client_count]->data , SECTOR_SIZE)!=SECTOR_SIZE)
							//printf("Writing to file sector data failed\n");
						//shared_addr[client_count]->buff
						//shared_addr[client_count]->sector_num= 10;
						shared_addr[client_count]->served = 1;

						if(msync(shared_addr[client_count], sizeof(test_struct), MS_SYNC|MS_INVALIDATE)==-1)
							printf("Msync error\n");


					}
					else if (shared_addr[client_count]->finished == 1)
					{

						delete_client(shared_addr, client_count);
						//Don't update iterator if you just deleted a client, because the next client is now in the
						//old client's place

						goto r_here;

					}


					++client_count;

		r_here:

						unblock_signal(SIGUSR1);		//Start listening out for client registration again
						//sleep(1);
					//signal(SIGUSR1, sig_handler);  //Start listening out for client registration


				}

			}
		}
		else
			printf("Fork failed!\n");

	}
	else
	{
		printf("I don't understand that command\n");
		return 0;
	}

	return 0;

}

/*
 * Read_sector function
 * @param sector_num
 * return 0 for success, -1 for failure
 */

int read_sector(int sector_num, byte * data)
{
	ssize_t test;
	size_t ps = getpagesize();
	//printf("Sector size / ps = %d", (SECTOR_SIZE/ps));
	test = posix_memalign((void**)&buffer, ps, SECTOR_SIZE);
	if (test!=0)
		printf("Alignment failed\n");
	off_t current_pos = lseek(sector_file, 0, SEEK_SET);  //reset the cursor_position to beginning

	current_pos = lseek(sector_file, sector_num*SECTOR_SIZE, SEEK_SET);//cursor position at the beginning of sector
	test = read(sector_file, buffer, SECTOR_SIZE);
	int i;
	for(i=0;i<SECTOR_SIZE;++i)
		data[i]= buffer[i];

	//memcpy(data,buffer, SECTOR_SIZE);

	if(SECTOR_SIZE == test)
		return 0;
	else
	{
		//fprintf( stderr, "Sector read failed:%s\n",
			//				            strerror( errno ) );
		printf("# of bytes read = %ld\n", test);
		printf("Current pos = %ld\n", current_pos);
		return -1;
	}


}

void sig_handler(int sig_num)
{
	signal(SIGALRM, SIG_IGN);  //Used for ignoring the signal
	block_signal(SIGUSR1);     //Block registration signals while I'm registering this user
	PRINT_RED;
	printf("Server received a signal...client has arrived\n");
	PRINT_WHITE;
	fseek(client_fp, 0,SEEK_SET);
	while(2!=fscanf(client_fp, "Client %d has arrived with %s\n", &client_pid, name)){};
	printf("Client's filename = %s\n", name);

	shm_id = shm_open(name, O_RDWR, 0);//don't think mode matters cause I'm not creator
	if(shm_id == -1)
	{
				fprintf( stderr, "Server shmOpen failed:%s\n",
					strerror( errno ) );
				//return EXIT_FAILURE;

	}


	shared_addr[active_clients] =(test_struct *) mmap(NULL,  //Let kernel find memory
					sizeof(test_struct),  //Lenght
					PROT_WRITE|PROT_READ, //Protection
					MAP_SHARED ,		//Flags
					shm_id,				//fid
					0);					//Offset

	if(shared_addr[active_clients] == MAP_FAILED)
		printf("Server mmap failed\n");

	printf("Client #%d in particular\n", client_pid);
	//printf("Read on the way\n");
	printf("shared_addr[%d]->sector_num = %d\n",active_clients, shared_addr[active_clients]->sector_num);

	//++client_count;
	++active_clients;

	unblock_signal(SIGUSR1);     //Block registration signals while I'm registering this user

	return;
}

void alarm_handler(int sig_num)
{
	printf("Timeout!...Closing down server\n");
	exit(0);
}

void install_sighandler(int signo, void (*handler)(int))
{
	sigset_t set;
	struct sigaction act;

	/* Setup the handler */
	act.sa_handler = handler;
	act.sa_flags = SA_RESTART;
	sigaction(signo, &act,0);

	/* Unblock the signal */
	sigemptyset(&set);
	sigaddset(&set, signo);
	sigprocmask(SIG_UNBLOCK, &set, NULL);

	return;
}

void block_signal(int signo)
{
	sigset_t set;

	/* Block the signal */
	sigemptyset(&set);
	sigaddset(&set, signo);
	sigprocmask(SIG_BLOCK, &set, NULL);

	return;
}

void unblock_signal(int signo)
{
	sigset_t set;

	/* Unblock the signal */
	sigemptyset(&set);
	sigaddset(&set, signo);
	sigprocmask(SIG_UNBLOCK, &set, NULL);

	return;
}

void delete_client(test_struct ** shared_addr, int index)
{
	//start at index and shift all elements to the left by one
	--active_clients;
	printf("Deleting %d  Active clients = %d\n", index, active_clients);
#ifdef DEBUG
	printf("Current client = %d", client_count);
#endif
	int i=0;
	munmap(shared_addr[index], sizeof(test_struct));
	//Shift everything left by 1
	if(index<active_clients)
	{
		for(i=index;i<active_clients;++i)
		{
			//memcpy(&shared_addr[index], &shared_addr[index+1], sizeof(test_struct));
			shared_addr[i] = shared_addr[i+1];
		}
	}

	return;


}




